/*
 * Author: nicolas.bredeche(@)upmc.fr
 * Created on 2014-01-06 
 */

package picoevo.app.chemicalreactions;

import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;

import picoevo.core.*;
import picoevo.ext.*;
import picoevo.tools.*;

public class Evolver_CR {
    public static void run()
    {
    	// // // // // //
    	//
		// 1 - Initialization (general)
    	//
    	// // // // // //
    	
    	Display.info("\n\n## initialization ##\n");
    	
    	int flushLogsCnt = 1; // flush log files every N generations. (useful if crash)
    	
    	ParameterSet properties = new ParameterSet();
    	
    	if ( properties.loadProperties("config/default.properties") == true )
    	{
    		Misc._nbOfAvailableProcessors = properties.getIntProperty("nbOfAvailableProcessors");
    		Misc.multithread = properties.getBooleanProperty("Multithreading");
    	}
    	else
    	{
    		int nb = Runtime.getRuntime().availableProcessors();
    		if ( nb > 4 )
    			Misc._nbOfAvailableProcessors =  nb-2;
    		else 
    			Misc._nbOfAvailableProcessors = nb>1?nb-1:1;
    	}
        
	   	properties.setProperty("InitPopSize",1);
    	properties.setProperty("Mu",500);
    	properties.setProperty("Lambda",500); // 9
    	properties.setProperty("Generations",1000000); // 100
        properties.setProperty("OptimisationFlag",Boolean.toString(Dictionary.Maximization));        
        properties.setProperty("Reevaluation",false);
        properties.setProperty("Elistist",true);
        properties.setProperty("PopulationRandomInit",false); // if false: init generation 0 with Lotka1920's oscillator
        properties.setProperty("LogAllIndividuals",true);
        
        properties.setProperty("PopulationStatisticsOperator",new StatisticsOperator_Population());
        properties.setProperty("LogFilename","logs/logfile_ChemicalReactions_"+Misc.getCurrentTimeAsCompactString());
 
	        
        properties.displayInformation();
        
        PrintWriter logfile_properties = null;
		try {
			logfile_properties = new PrintWriter(properties.getProperty("LogFilename") + ".properties", "UTF-8");
		} catch (FileNotFoundException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); }

		String s = "";
        s += properties.getString() + "\n";
        s += "-- listing global parameters --\n";
        s += Global_CR.getString();
        logfile_properties.write(s);
        logfile_properties.close();
                
        PrintWriter logfile_all = null;
        boolean logAllIndividuals = properties.getBooleanProperty("LogAllIndividuals");
        if ( logAllIndividuals )
        {
			try {
				logfile_all = new PrintWriter(properties.getProperty("LogFilename") + "_all" + ".data", "UTF-8");
			} catch (FileNotFoundException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); }
        }

		PrintWriter logfile_best = null;
		try {
			logfile_best = new PrintWriter(properties.getProperty("LogFilename") + "_best" + ".data", "UTF-8");
		} catch (FileNotFoundException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); }

        PrintWriter logfile = null;
		try {
			logfile = new PrintWriter(properties.getProperty("LogFilename") + ".data", "UTF-8");
		} catch (FileNotFoundException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); }

		s = "";
        s += "# generationCnt, evaluationCnt, max, quartile_sup, median, quartile_inf, min.\n"; 
        logfile.write(s);

        // // // // // //
        //
        // 2 - Initialization (population)
        //
        // // // // // //
        
        Population population = new Population("Population of chemical reactions");
        population.setProperties(properties);
        population.setReevaluate(population.getProperties().getBooleanProperty("Reevaluation")); // always reevaluate old individuals (if any)

        for ( int i = 0 ; i != population.getProperties().getIntProperty("InitPopSize") ; i++ )
        {
        	if ( population.getProperties().getBooleanProperty("PopulationRandomInit") == true )
        	{
        		// register random individual
        		population.add(new Individual_CR(population));
        	}
        	else
        	{
        		// register seeded individual
        		population.add(Global_CR.returnDefaultIndividual(population));
        	}
		}
	    
        // // // // // // //
        //
		// 3 - Evolution
        //
        // // // // // // //
        
		Display.info("\n\n## evolving ##\n");
	
		int displayFreq = 1;
		
		int mu = population.getProperties().getIntProperty("Mu");
		int lambda = population.getProperties().getIntProperty("Lambda");
		
		int generation = 0;
		int evaluations = 0;
		
		for ( ; generation != population.getProperties().getIntProperty("Generations") ; generation++ )
		{
			Display.info("\n# Generation " + generation + " (evaluation: " + evaluations + ")\n");

			int evaluatedIndividuals = population.evaluate();

			// log all new individuals of current generation
			
			if ( logAllIndividuals )
			{
				int i = 0;
				
				if ( generation > 1 )
				{
					if ( population.getProperties().getBooleanProperty("Elistist") == true && population.getProperties().getBooleanProperty("Reevaluation") == false )
						i = mu; // skip the mu parents
				}

				if ( population.getProperties().getBooleanProperty("Elistist") == true && population.getProperties().getBooleanProperty("Reevaluation") == false )
				{
					if ( generation > 1 )
					{
						i = mu; // skip the mu parents (ie. do not log individuals which were already evaluated)
					}
					else
					{
						if ( generation == 1 )
						{
							i = population.getProperties().getIntProperty("InitPopSize"); // skip the original parents from generation 0 (ie. do not log individuals which were already evaluated) 
						}
					}
				}

				for ( ; i != population.getSize() ; i++ )
				{
		            s = "";
		            s += "Generation " + generation + "\n"; 
		            s += "individual (fitness value: " + population.getIndividual(i).getFitness() + ")\n";
		            s += ((Individual_CR)population.getIndividual(i)).getDescription();
		            s += "---\n";
		            logfile_all.write(s);
				}
			}
			
	        if (generation % displayFreq == 0)
	        {	
	        	//population.displayInformation();
	        	population.displayInformationCompact();
	        	population.displayStatistics();
				Display.memorycheck(); // Debug
	        }
	        
			PopulationContainer newpop; // population for the next generation
			PopulationView populationView;
			if ( population.getProperties().getIntProperty("InitPopSize") < mu && generation == 0)
			{
				populationView = SelectionOperator_rankBased.apply(population, population.getProperties().getIntProperty("InitPopSize")); // returns: pointers to the MU best individuals
			}
			else
			{
				populationView = SelectionOperator_rankBased.apply(population, mu); // returns: pointers to the MU best individuals	
			}
			
			if ( population.getProperties().getBooleanProperty("Elistist") )
				newpop = new PopulationContainer (populationView); // Elitist: Keep the best parents.
			else
				newpop = new PopulationContainer (); // dont keep parents
			
			// log best of current generation
            s = "";
            s += "Generation " + generation + " (evaluation: " + evaluations + ")\n"; 
            s += "Best evolved individual (fitness value: " + populationView.getIndividual(0).getFitness() + ")\n";
            s += ((Individual_CR)populationView.getIndividual(0)).getDescription();
            s += "---\n";
            logfile_best.write(s);

            // log generation
			PopulationView populationViewFull = SelectionOperator_rankBased.apply(population,population.getSize()); // returns: pointers to the MU best individuals
            s = "";
            s += "" + generation;
            s += ",";
            s += "" + evaluations;
            s += ",";
            s += populationViewFull.getIndividual(0).getFitness(); // best
            s += ",";
            s += populationViewFull.getIndividual((populationViewFull.getSize()-1)*3/4).getFitness(); // 75% quartile
            s += ",";
            s += populationViewFull.getIndividual((populationViewFull.getSize()-1)/2).getFitness(); // median
            s += ",";
            s += populationViewFull.getIndividual((populationViewFull.getSize()-1)*1/4).getFitness(); // 25% quartile
            s += ",";
            s += populationViewFull.getIndividual(populationViewFull.getSize()-1).getFitness(); // worst
            s += ".\n";
            logfile.write(s);
			
			for ( int i = 0 ; i != lambda ; i++ ) // generate (and register) lambda offspring. 
			{
				//Individual_ChemReactions child = new Individual_ChemReactions(population); // generate random offspring
				int selectedParent;
				if ( generation == 0 )
				{
					if ( population.getProperties().getIntProperty("InitPopSize") < mu)
						selectedParent = (int) (Math.random()*population.getProperties().getIntProperty("InitPopSize"));
					else
						selectedParent = (int) (Math.random()*(double)mu);
				}
				else
					selectedParent = (int) (Math.random()*(double)mu);
				Display.info("selected parent #" + selectedParent);
				Individual_CR child = (Individual_CR) populationView.getIndividual(selectedParent).clone();
				child.updateId();
				child.setEvaluationFlag(false);
				
				int nbTry = 0;
				int nbTryMax = 10;
				do {
					if ( nbTry == nbTryMax ) // this is assumed to be *very* unlikely
					{
						Display.debug("Mutate individual failed after " + nbTry + " tentative.");
						//System.exit(-1);
					}
				} while	( child.mutate() == 0 );  // force at least mutation 
				
				newpop.add(child);
			}

			evaluations += evaluatedIndividuals;

			population.renew(newpop); // replace old population with the new one (remark: newpop is emptied)
	        	        
	        Display.info("\n# =-=-=-=");
	        
	        // flush files

	        /**/
	        if ( generation > 0 && generation%flushLogsCnt == 0 )
	        {
				if ( logAllIndividuals )
					logfile_all.close();
	        	logfile_best.close();
		        logfile.close();

				if ( logAllIndividuals )
				{
					try {
						logfile_all  = new PrintWriter( new BufferedWriter( new FileWriter( properties.getProperty("LogFilename") + "_all" + ".data", true ) ) );
					} catch (FileNotFoundException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) {
						e.printStackTrace();
					}
				}
		        
		        try {
					logfile_best  = new PrintWriter( new BufferedWriter( new FileWriter( properties.getProperty("LogFilename") + "_best" + ".data", true ) ) );
				} catch (FileNotFoundException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) {
					e.printStackTrace();
				}
	
				try {
					logfile  = new PrintWriter( new BufferedWriter( new FileWriter( properties.getProperty("LogFilename") + ".data", true ) ) );
				} catch (FileNotFoundException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) {
					e.printStackTrace();
				}
	        }
	        /**/

		}
		
		// last generation
		
		Display.info("\n# Generation " + generation + " (evaluation: " + evaluations + ")\n");

		population.evaluate();
		
        if (generation % displayFreq == 0)
        {
        	population.displayInformation();
        	population.displayStatistics();
        }
	
        Display.info("\n=-=-=-=\n\n");
        
        // log best of last generation
        PopulationView populationView = SelectionOperator_rankBased.apply(population, mu);
        s = "";
        s += "Generation " + generation + " (evaluation: " + evaluations + ")\n"; 
        s += "Best evolved individual (fitness value: " + populationView.getIndividual(0).getFitness() + ")\n";
        s += ((Individual_CR)populationView.getIndividual(0)).getDescription();
        s += "---\n"; 
        logfile_best.write(s);
        Display.info(s);
	
        // log generation
		PopulationView populationViewFull = SelectionOperator_rankBased.apply(population,population.getSize()); // returns: pointers to the MU best individuals
        s = "";
        s += "" + generation;
        s += ",";
        s += "" + evaluations;
        s += ",";
        s += populationViewFull.getIndividual(0).getFitness(); // best
        s += ",";
        s += populationViewFull.getIndividual((populationViewFull.getSize()-1)*3/4).getFitness(); // 75% quartile
        s += ",";
        s += populationViewFull.getIndividual((populationViewFull.getSize()-1)/2).getFitness(); // median
        s += ",";
        s += populationViewFull.getIndividual((populationViewFull.getSize()-1)*1/4).getFitness(); // 25% quartile
        s += ",";
        s += populationViewFull.getIndividual(populationViewFull.getSize()-1).getFitness(); // worst
        s += ".\n";
        logfile.write(s);
		
        Display.info("\n=-=-=-=\n");

        Display.memorycheck();

		if ( logAllIndividuals )
			logfile_all.close();
        logfile_best.close();
        logfile.close();

	}
    
    public static void main(String[] args) {
        double startTime = System.currentTimeMillis();
		
        Version.displayCurrentReleaseInformation();
		
        Display.info("###PicoEvo :: Chemical Reactions###");
        Display.info("Running...\n\n");        
        run(); 	
        Display.info("Stop ("+ ((System.currentTimeMillis()-startTime)/1000) +"s).");
    }


}
